from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext import db
from google.appengine.api import users

import base64
import os
import re
import time
import twitter

from Cheetah.Template import Template

class AccessError(StandardError):
    pass

class Setting(db.Model):
    setting_key = db.StringProperty(required=True)
    setting_value = db.StringProperty(required=True)

class Whitelist(db.Model):
    username = db.StringProperty(required=True)

def enable_white_list(value):
    wl = Setting.all().filter('setting_key =', 'use_white_list').fetch(1)
    if wl:
        wl[0].setting_value = value
        wl[0].put()
    else:
        setting = Setting(setting_key='use_white_list', setting_value=value)
        setting.put()

def use_white_list():
    wl = Setting.all().filter('setting_key =', 'use_white_list').fetch(1)
    if wl:
        return wl[0].setting_value=='True'
    return False

def is_allow(username):
    if not use_white_list():
        return True
    list = Whitelist.all().filter('username =', username).fetch(1)
    return len(list)==1

def get_cookie(request, name):
    '''
    Get cookie by name.
    
    Args:
        name: cookie name as string.
    
    Returns:
        Cookie value, or None if not exist.
    '''
    if not 'Cookie' in request.headers:
        return None
    all = request.headers['Cookie']
    if all is None:
        return None
    cookies = all.split(';')
    key = name + '='
    for cookie in cookies:
        c = cookie.strip()
        if c.startswith(key):
            return c[len(key):]
    return None

def set_cookie(response, name, value, max_age=-1, path='/', domain=None, secure=False):
    '''
    Set a cookie to client.
    
    Args:
        name: cookie name.
        value: cookie value.
        max_age: cookie age in seconds, if 0, cookie will be deleted immediately, if <0, ignored. Default to -1.
        path: cookie path, default to '/'.
        domain: domain name, default to None and ignored.
        secure: if cookie is secure, default to False.
    
    Returns:
        None, but cookie was added to response headers.
    '''
    cookie = name + '=' + value + '; path=' + path
    if max_age>=0:
        cookie += time.strftime('; expires=%a, %d-%b-%Y %H:%M:%S GMT', time.gmtime(time.time() + max_age))
    if domain is not None:
        cookie = cookie + '; domain=' + domain
    if secure:
        cookie += '; secure'
    response.headers.add_header('Set-Cookie', cookie)

def remove_cookie(response, name, path='/', domain=None, secure=False):
    '''
    Remove cookie by name.
    
    Args:
        name: cookie name.
        path: cookie path, default to '/'.
        domain: domain name, default to None and ignored.
        secure: if cookie is secure, default to False.
    
    Returns:
        None, but cookie was added to response headers and should be removed in client.
    '''
    set_cookie(response, name, 'deleted', 0, path, domain, secure)

re_at = re.compile(r'\@[a-zA-Z0-9]+')
re_sharp = re.compile(r'\#[a-zA-Z0-9]+')
re_http = re.compile(r'https?\:\/\/[a-zA-Z0-9\_\-\.\/\%\?\&\#\=]+')

def __create_links(matchobj):
    link = matchobj.group(0)
    return r'<a href="%s" target="_blank">%s</a>' % (link, link)

def __create_ats(matchobj):
    link = matchobj.group(0)[1:]
    return r'@<a href="/%s">%s</a>' % (link, link)

def __create_sharps(matchobj):
    link = matchobj.group(0)[1:]
    return r'#<a href="/list/%s">%s</a>' % (link, link)

def make_hyperlinks(text):
    text = re_http.sub(__create_links, text)
    return re_at.sub(__create_ats, text)

class Dispatcher(webapp.RequestHandler):

    def __init__(self):
        super(Dispatcher, self).__init__(self)
        self.reserved = {
                '/' : self.__home,
                '/login' : self.__login,
                '/logout' : self.__logout,
                '/status/update' : self.__update,
                '/friend/follow' : self.__follow,
                '/admin/whitelist' : self.__whitelist
        }

    def get(self):
        self.__handle('get')

    def post(self):
        self.__handle('post')

    def handle_exception(self, exception, debug_mode):
        if isinstance(exception, AccessError):
            return self.__redirect('/static/403.html')
        if isinstance(exception, twitter.TwitterError):
            self.error(500)
            self.response.clear()
            self.response.out.write(exception.message)
            return
        super(Dispatcher, self).handle_exception(exception, debug_mode)

    def __handle(self, method):
        path = self.request.path
        if path in self.reserved:
            return self.reserved[path](method)
        self.__user(path[1:])

    def __home(self, method):
        credentials = self.__get_credentials()
        if credentials is None:
            return self.__redirect('https://' + self.request.environ['SERVER_NAME'] + '/login')
        self.__check_whitelist(credentials[0])
        api = twitter.Api(credentials[0], credentials[1])
        username = credentials[0]
        user = ('@' in username) and api.GetUserByEmail(username) or api.GetUser(username)
        statuses = api.GetFriendsTimeline(credentials[0], count=100)
        # get friends of current authenticated user:
        friends = api.GetFriends()
        self.__render('home.html', { 'user' : user, 'statuses': statuses, 'friends' : friends })

    def __check_whitelist(self, username):
        if not is_allow(username):
            raise AccessError()

    def __login(self, method):
        if method=='get':
            return self.__render('login.html')
        username = self.request.get('username', '').strip()
        password = self.request.get('password', '')
        expires = int(self.request.get('expires', '604800'))
        if not username or not password:
            return self.__render('login.html', { 'error' : 'Wrong Username/Email and password combination.' } )
        self.__check_whitelist(username)
        api = twitter.Api(username, password)
        try:
            api.VerifyCredentials()
        except twitter.TwitterError, e:
            return self.__render('login.html', { 'error' : e.message })
        self.response.set_status(302)
        self.response.headers['Location'] = 'http://' + self.request.environ['SERVER_NAME'] + '/'
        credentials = base64.b64encode(username + ':' + password)
        set_cookie(self.response, 'credentials', credentials, expires)

    def __logout(self, method):
        self.response.set_status(302)
        self.response.headers['Location'] = 'https://' + self.request.environ['SERVER_NAME'] + '/login'
        remove_cookie(self.response, 'credentials')

    def __update(self, method):
        credentials = self.__get_credentials()
        if credentials is None:
            return self.__redirect('https://' + self.request.environ['SERVER_NAME'] + '/login')
        self.__check_whitelist(credentials[0])
        status = self.request.get('status', '(Empty)')
        api = twitter.Api(credentials[0], credentials[1])
        api.PostUpdate(status)
        return self.__home(method)

    def __follow(self, method):
        credentials = self.__get_credentials()
        if credentials is None:
            return self.__redirect('https://' + self.request.environ['SERVER_NAME'] + '/login')
        self.__check_whitelist(credentials[0])
        username = self.request.get('username', '')
        if username:
            api = twitter.Api(credentials[0], credentials[1])
            api.CreateFriendship(username)
        return self.__user(username)

    def __user(self, username):
        credentials = self.__get_credentials()
        if credentials is None:
            return self.__redirect('https://' + self.request.environ['SERVER_NAME'] + '/login')
        self.__check_whitelist(credentials[0])
        api = twitter.Api(credentials[0], credentials[1])
        user = api.GetUser(username)
        friends = api.GetFriends(username)
        is_me = username==credentials[0]
        is_friend = False
        if not is_me:
            is_friend = api.GetFriendshipExists(credentials[0], username)
        statuses = api.GetUserTimeline(username, 100)
        model = {
                'user' : user,
                'statuses' : statuses,
                'is_me' : is_me,
                'is_friend': is_friend,
                'friends' : friends
        }
        self.__render('user.html', model)

    def __render(self, template, model={}):
        model['make_hyperlinks'] = make_hyperlinks
        root = os.path.dirname(__file__)
        path = os.path.join(root, template)
        t = Template(file=path, searchList=[model])
        self.response.out.write(t)

    def __get_credentials(self):
        '''
        Get username and password from base64-encoded cookie as tuple 
        (username, password), or None if no valid cookie found.
        '''
        value = get_cookie(self.request, 'credentials')
        if value is None:
            return None
        dec = base64.b64decode(value)
        ss = dec.split(':')
        if len(ss)!=2:
            return None
        return (ss[0], ss[1])

    def __redirect(self, url):
        self.response.set_status(302)
        self.response.headers['Location'] = url

    def __whitelist(self, method):
        # check if admin:
        user = users.get_current_user()
        if user is None or not users.is_current_user_admin():
            return self.__redirect(users.create_login_url('/admin/whitelist'))
        if method=='get':
            # display list:
            status = str(use_white_list())
            list = Whitelist.all().fetch(1000)
            return self.__render('whitelist.html', { 'status' : status, 'whitelist' : list })
        # add new account:
        u = self.request.get('username', '').strip()
        if u and not is_allow(u):
            w = Whitelist(username=u)
            w.put()
        enabled = self.request.get('enabled', '')
        if enabled:
            enable_white_list(enabled)
        self.__redirect('/admin/whitelist')

application = webapp.WSGIApplication([('^/.*$', Dispatcher)], debug=True)

def main():
    run_wsgi_app(application)

if __name__ == "__main__":
    main()
